/*-------------------------------------------------------------------------
 *
 * smgr.c
 *	  存储管理器切换的公共接口例程。
 *
 *	  所有的文件系统操作在 POSTGRES 中都通过这些
 *	  例程进行分发。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/smgr/smgr.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/xlogutils.h"
#include "lib/ilist.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/md.h"
#include "storage/smgr.h"
#include "utils/hsearch.h"
#include "utils/inval.h"


/*
 * 这个函数指针的结构定义了 smgr.c 与
 * 任何单独存储管理器模块之间的 API。注意，smgr 子函数通常
 * 预计通过 elog(ERROR) 报告问题。一个例外是
 * smgr_unlink 应该使用 elog(WARNING)，而不是报告错误，
 * 因为我们通常在提交/中止清理期间解除链接关系，
 * 因此已为时已晚，无法引发错误。还应允许在引导和/或 WAL
 * 恢复期间发生的各种条件，这在 md.c 中有详细评论。
 */
typedef struct f_smgr
{
	void		(*smgr_init) (void);	/* 可能为 NULL */
	void		(*smgr_shutdown) (void);	/* 可能为 NULL */
	void		(*smgr_open) (SMgrRelation reln);
	void		(*smgr_close) (SMgrRelation reln, ForkNumber forknum);
	void		(*smgr_create) (SMgrRelation reln, ForkNumber forknum,
								bool isRedo);
	bool		(*smgr_exists) (SMgrRelation reln, ForkNumber forknum);
	void		(*smgr_unlink) (RelFileNodeBackend rnode, ForkNumber forknum,
								bool isRedo);
	void		(*smgr_extend) (SMgrRelation reln, ForkNumber forknum,
								BlockNumber blocknum, char *buffer, bool skipFsync);
	bool		(*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum,
								  BlockNumber blocknum);
	void		(*smgr_read) (SMgrRelation reln, ForkNumber forknum,
							  BlockNumber blocknum, char *buffer);
	void		(*smgr_write) (SMgrRelation reln, ForkNumber forknum,
							   BlockNumber blocknum, char *buffer, bool skipFsync);
	void		(*smgr_writeback) (SMgrRelation reln, ForkNumber forknum,
								   BlockNumber blocknum, BlockNumber nblocks);
	BlockNumber (*smgr_nblocks) (SMgrRelation reln, ForkNumber forknum);
	void		(*smgr_truncate) (SMgrRelation reln, ForkNumber forknum,
								  BlockNumber old_blocks, BlockNumber nblocks);
	void		(*smgr_immedsync) (SMgrRelation reln, ForkNumber forknum);
} f_smgr;

static const f_smgr smgrsw[] = {
	/* 磁盘 */
	{
		.smgr_init = mdinit,
		.smgr_shutdown = NULL,
		.smgr_open = mdopen,
		.smgr_close = mdclose,
		.smgr_create = mdcreate,
		.smgr_exists = mdexists,
		.smgr_unlink = mdunlink,
		.smgr_extend = mdextend,
		.smgr_prefetch = mdprefetch,
		.smgr_read = mdread,
		.smgr_write = mdwrite,
		.smgr_writeback = mdwriteback,
		.smgr_nblocks = mdnblocks,
		.smgr_truncate = mdtruncate,
		.smgr_immedsync = mdimmedsync,
	}
};

static const int NSmgr = lengthof(smgrsw);

/*
 * 每个后端都有一个哈希表，用于存储所有存在的 SMgrRelation 对象。
 * 此外，“无人拥有”的 SMgrRelation 对象以链表的形式链接在一起。
 */
static HTAB *SMgrRelationHash = NULL;

static dlist_head unowned_relns;

/* 本地函数原型 */
static void fc_smgrshutdown(int fc_code, Datum fc_arg);



/*
 *	smgrinit(), smgrshutdown() -- 初始化或关闭存储管理器。
 *
 * 注意：smgrinit 在后端启动时调用（正常或独立案例），*不是* 在主进程启动时。 因此，这里创建或在 smgrshutdown 中销毁的任何资源都是后端本地的。
 */
void smgrinit(void)
{
	int			fc_i;

	for (fc_i = 0; fc_i < NSmgr; fc_i++)
	{
		if (smgrsw[fc_i].smgr_init)
			smgrsw[fc_i].smgr_init();
	}

	/* 注册关闭过程 */
	on_proc_exit(fc_smgrshutdown, 0);
}

/*
 * 在后端关闭期间用于 smgr 清理的 on_proc_exit 钩子
 */
static void fc_smgrshutdown(int fc_code, Datum fc_arg)
{
	int			fc_i;

	for (fc_i = 0; fc_i < NSmgr; fc_i++)
	{
		if (smgrsw[fc_i].smgr_shutdown)
			smgrsw[fc_i].smgr_shutdown();
	}
}

/*
 *	smgropen() -- 返回一个 SMgrRelation 对象，如果需要则创建它。
 *
 *		这并不尝试实际打开底层文件。
 */
SMgrRelation
smgropen(RelFileNode fc_rnode, BackendId fc_backend)
{
	RelFileNodeBackend fc_brnode;
	SMgrRelation fc_reln;
	bool		fc_found;

	if (SMgrRelationHash == NULL)
	{
		/* 第一次遍历：初始化哈希表 */
		HASHCTL		fc_ctl;

		fc_ctl.keysize = sizeof(RelFileNodeBackend);
		fc_ctl.entrysize = sizeof(SMgrRelationData);
		SMgrRelationHash = hash_create("smgr relation table", 400,
									   &fc_ctl, HASH_ELEM | HASH_BLOBS);
		dlist_init(&unowned_relns);
	}

	/* 查找或创建一个条目 */
	fc_brnode.node = fc_rnode;
	fc_brnode.backend = fc_backend;
	fc_reln = (SMgrRelation) hash_search(SMgrRelationHash,
									  (void *) &fc_brnode,
									  HASH_ENTER, &fc_found);

	/* 如果之前不存在则初始化它 */
	if (!fc_found)
	{
		/* hash_search 已经填入查找键 */
		fc_reln->smgr_owner = NULL;
		fc_reln->smgr_targblock = InvalidBlockNumber;
		for (int fc_i = 0; fc_i <= MAX_FORKNUM; ++fc_i)
			fc_reln->smgr_cached_nblocks[fc_i] = InvalidBlockNumber;
		fc_reln->smgr_which = 0;	/* 目前我们只有 md.c */

		/* 特定于实现的初始化 */
		smgrsw[fc_reln->smgr_which].smgr_open(fc_reln);

		/* 它还没有所有者 */
		dlist_push_tail(&unowned_relns, &fc_reln->node);
	}

	return fc_reln;
}

/*
 * smgrsetowner() -- 建立对 SMgrRelation 对象的长期引用
 *
 * 同时只能有一个所有者；这就足够了，因为目前唯一这样的所有者存在于 relcache 中。
 */
void smgrsetowner(SMgrRelation *fc_owner, SMgrRelation fc_reln)
{
	/* 我们不支持在这里“放弃”一个 SMgrRelation，使用 smgrclearowner */
	Assert(fc_owner != NULL);

	/*
	 * 首先，解除任何旧所有者的挂钩。 （正常情况下不应该有旧所有者，但
	 * 在 swap_relation_files() 处理过程中，根据处理顺序，似乎有可能发生这种情况。
	 * 在这种情况下，尽早关闭旧的 relcache 条目没有问题。）
	 *
	 * 如果没有旧所有者，则 reln 应该在未拥有列表中，我们需要将其移除。
	 */
	if (fc_reln->smgr_owner)
		*(fc_reln->smgr_owner) = NULL;
	else
		dlist_delete(&fc_reln->node);

	/* 现在建立所有权关系。 */
	fc_reln->smgr_owner = fc_owner;
	*fc_owner = fc_reln;
}

/*
 * smgrclearowner() -- 如果存在，则删除对 SMgrRelation 对象的长期引用
 */
void smgrclearowner(SMgrRelation *fc_owner, SMgrRelation fc_reln)
{
	/* 如果 SMgrRelation 对象不由所有者拥有，则不执行任何操作 */
	if (fc_reln->smgr_owner != fc_owner)
		return;

	/* 取消设置所有者的引用 */
	*fc_owner = NULL;

	/* 取消设置我们对所有者的引用 */
	fc_reln->smgr_owner = NULL;

	/* 添加到未拥有关系的列表中 */
	dlist_push_tail(&unowned_relns, &fc_reln->node);
}

/*
 *	smgrexists() -- 对于一个分支，底层文件是否存在？
 */
bool smgrexists(SMgrRelation fc_reln, ForkNumber fc_forknum)
{
	return smgrsw[fc_reln->smgr_which].smgr_exists(fc_reln, fc_forknum);
}

/*
 *	smgrclose() -- 关闭并删除一个 SMgrRelation 对象。
 */
void smgrclose(SMgrRelation fc_reln)
{
	SMgrRelation *fc_owner;
	ForkNumber	fc_forknum;

	for (fc_forknum = 0; fc_forknum <= MAX_FORKNUM; fc_forknum++)
		smgrsw[fc_reln->smgr_which].smgr_close(fc_reln, fc_forknum);

	fc_owner = fc_reln->smgr_owner;

	if (!fc_owner)
		dlist_delete(&fc_reln->node);

	if (hash_search(SMgrRelationHash,
					(void *) &(fc_reln->smgr_rnode),
					HASH_REMOVE, NULL) == NULL)
		elog(ERROR, "SMgrRelation hashtable corrupted");

	/*
	 * 如果有，解除所有者指针的挂钩。 我们最后进行此操作，因为在上面的
	 * 失败的远程可能性中，SMgrRelation 对象仍然存在。
	 */
	if (fc_owner)
		*fc_owner = NULL;
}

/*
 *	smgrrelease() -- 释放该对象使用的所有资源。
 *
 *	该对象保持有效。
 */
void smgrrelease(SMgrRelation fc_reln)
{
	for (ForkNumber fc_forknum = 0; fc_forknum <= MAX_FORKNUM; fc_forknum++)
	{
		smgrsw[fc_reln->smgr_which].smgr_close(fc_reln, fc_forknum);
		fc_reln->smgr_cached_nblocks[fc_forknum] = InvalidBlockNumber;
	}
	fc_reln->smgr_targblock = InvalidBlockNumber;
}

/*
 *	smgrreleaseall() -- 释放所有对象使用的资源。
 *
 *	这对于 PROCSIGNAL_BARRIER_SMGRRELEASE 被调用。
 */
void smgrreleaseall(void)
{
	HASH_SEQ_STATUS fc_status;
	SMgrRelation fc_reln;

	/* 如果哈希表未设置，则无需执行任何操作 */
	if (SMgrRelationHash == NULL)
		return;

	hash_seq_init(&fc_status, SMgrRelationHash);

	while ((fc_reln = (SMgrRelation) hash_seq_search(&fc_status)) != NULL)
		smgrrelease(fc_reln);
}

/*
 *	smgrcloseall() -- 关闭所有现有的 SMgrRelation 对象。
 */
void smgrcloseall(void)
{
	HASH_SEQ_STATUS fc_status;
	SMgrRelation fc_reln;

	/* 如果哈希表未设置，则无需执行任何操作 */
	if (SMgrRelationHash == NULL)
		return;

	hash_seq_init(&fc_status, SMgrRelationHash);

	while ((fc_reln = (SMgrRelation) hash_seq_search(&fc_status)) != NULL)
		smgrclose(fc_reln);
}

/*
 *	smgrclosenode() -- 关闭给定 RelFileNode 的 SMgrRelation 对象，
 *					   如果存在的话。
 *
 * 它的效果与 smgrclose(smgropen(rnode)) 相同，但可以避免
 * 不必要地创建哈希表条目然后在没有此类条目的情况下将其删除。
 */
void smgrclosenode(RelFileNodeBackend fc_rnode)
{
	SMgrRelation fc_reln;

	/* 如果哈希表未设置，则无需执行任何操作 */
	if (SMgrRelationHash == NULL)
		return;

	fc_reln = (SMgrRelation) hash_search(SMgrRelationHash,
									  (void *) &fc_rnode,
									  HASH_FIND, NULL);
	if (fc_reln != NULL)
		smgrclose(fc_reln);
}

/*
 *	smgrcreate() -- 创建一个新关系。
 *
 *		给定一个已经创建（但可能未使用）的 SMgrRelation，
 *		使其底层磁盘文件或其他存储为分支创建。
 */
void smgrcreate(SMgrRelation fc_reln, ForkNumber fc_forknum, bool fc_isRedo)
{
	smgrsw[fc_reln->smgr_which].smgr_create(fc_reln, fc_forknum, fc_isRedo);
}

/*
 *	smgrdosyncall() -- 立即同步所有给定关系的所有分支
 *
 *		所有给定关系的所有分支都已同步到存储。
 *
 *		这等同于对每个 smgr 关系调用 FlushRelationBuffers()，
 *		然后对每个关系的所有分支调用 smgrimmedsync()，但它的
 *		速度明显更快，因此在可能的情况下应首选。
 */
void smgrdosyncall(SMgrRelation *fc_rels, int fc_nrels)
{
	int			fc_i = 0;
	ForkNumber	fc_forknum;

	if (fc_nrels == 0)
		return;

	FlushRelationsAllBuffers(fc_rels, fc_nrels);

	/*
	 * 同步物理文件。
	 */
	for (fc_i = 0; fc_i < fc_nrels; fc_i++)
	{
		int			fc_which = fc_rels[fc_i]->smgr_which;

		for (fc_forknum = 0; fc_forknum <= MAX_FORKNUM; fc_forknum++)
		{
			if (smgrsw[fc_which].smgr_exists(fc_rels[fc_i], fc_forknum))
				smgrsw[fc_which].smgr_immedsync(fc_rels[fc_i], fc_forknum);
		}
	}
}

/*
 *	smgrdounlinkall() -- 立即取消链接所有给定关系的所有分支
 *
 *		所有给定关系的所有分支都从存储中删除。 这
 *		不应在事务操作期间使用，因为它无法撤销。
 *
 *		如果 isRedo 为真，底层文件已经消失也是可以的。
 */
void smgrdounlinkall(SMgrRelation *fc_rels, int fc_nrels, bool fc_isRedo)
{
	int			fc_i = 0;
	RelFileNodeBackend *fc_rnodes;
	ForkNumber	fc_forknum;

	if (fc_nrels == 0)
		return;

	/*
	 * 清除与关系相关的任何剩余缓冲区。 bufmgr将仅
	 * 丢弃它们而不写入内容。
	 */
	DropRelFileNodesAllBuffers(fc_rels, fc_nrels);

	/*
	 * 创建一个数组，其中包含所有要删除的关系，并在此过程中关闭
	 * 每个关系的分叉在 smgr 级别。
	 */
	fc_rnodes = palloc(sizeof(RelFileNodeBackend) * fc_nrels);
	for (fc_i = 0; fc_i < fc_nrels; fc_i++)
	{
		RelFileNodeBackend fc_rnode = fc_rels[fc_i]->smgr_rnode;
		int			fc_which = fc_rels[fc_i]->smgr_which;

		fc_rnodes[fc_i] = fc_rnode;

		/* 在 smgr 级别关闭分叉 */
		for (fc_forknum = 0; fc_forknum <= MAX_FORKNUM; fc_forknum++)
			smgrsw[fc_which].smgr_close(fc_rels[fc_i], fc_forknum);
	}

	/*
	 * 发送一个共享无效消息，以强制其他后端关闭它们可能拥有的任何
	 * 悬空的 smgr 引用。这应该在开始实际的 unlinking 之前进行，以防万一
	 * 我们在这个步骤中失败。注意，sinval 消息最终也会返回到这个后端，
	 * 从而提供一个后备，确保我们关闭了自己的 smgr 关系。
	 */
	for (fc_i = 0; fc_i < fc_nrels; fc_i++)
		CacheInvalidateSmgr(fc_rnodes[fc_i]);

	/*
	 * 删除物理文件。
	 *
	 * 注意：smgr_unlink 必须将删除失败视为警告，而不是错误，
	 * 因为我们已经决定提交或中止当前的事务。
	 */

	for (fc_i = 0; fc_i < fc_nrels; fc_i++)
	{
		int			fc_which = fc_rels[fc_i]->smgr_which;

		for (fc_forknum = 0; fc_forknum <= MAX_FORKNUM; fc_forknum++)
			smgrsw[fc_which].smgr_unlink(fc_rnodes[fc_i], fc_forknum, fc_isRedo);
	}

	pfree(fc_rnodes);
}


/*
 *	smgrextend() -- 向文件添加一个新块。
 *
 *		语义几乎与 smgrwrite() 相同：在指定位置写入。
 *		然而，这用于扩展关系的情况（即，块编号位于当前
 *		EOF 或之后）。注意，我们假设在当前 EOF 之外写入块
 *		会导致中间文件空间填充零。
 */
void smgrextend(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blocknum,
		   char *fc_buffer, bool fc_skipFsync)
{
	smgrsw[fc_reln->smgr_which].smgr_extend(fc_reln, fc_forknum, fc_blocknum,
										 fc_buffer, fc_skipFsync);

	/*
	 * 通常我们期望这会使 nblocks 增加一个，但如果缓存的
	 * 值与预期不符，只需使其失效，以便下一个调用请求
	 * 内核。
	 */
	if (fc_reln->smgr_cached_nblocks[fc_forknum] == fc_blocknum)
		fc_reln->smgr_cached_nblocks[fc_forknum] = fc_blocknum + 1;
	else
		fc_reln->smgr_cached_nblocks[fc_forknum] = InvalidBlockNumber;
}

/*
 *	smgrprefetch() -- 启动对指定关系块的异步读取。
 *
 *		仅在恢复中，这可以返回 false，表示文件
 *		不存在（推测是由于后续的 WAL 记录被删除）。
 */
bool smgrprefetch(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blocknum)
{
	return smgrsw[fc_reln->smgr_which].smgr_prefetch(fc_reln, fc_forknum, fc_blocknum);
}

/*
 *	smgrread() -- 从关系中读取特定块到提供的
 *				  缓冲区。
 *
 *		此例程由缓冲区管理器调用，以便在共享缓冲区缓存中
 *		实例化页面。所有存储管理器以 POSTGRES 期望的格式返回页面。
 */
void smgrread(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blocknum,
		 char *fc_buffer)
{
	smgrsw[fc_reln->smgr_which].smgr_read(fc_reln, fc_forknum, fc_blocknum, fc_buffer);
}

/*
 *	smgrwrite() -- 写入提供的缓冲区。
 *
 *		这仅用于更新已存在的关系块（即，当前 EOF 之前的那些）。
 *		要扩展关系，请使用 smgrextend()。
 *
 *		这不是一个同步写入——返回时块不一定在磁盘上，
 *		只是被转储到内核中。然而，将为下一个检查点之前
 *		同步写入做出规定。
 *
 *		skipFsync 表示调用者将采取其他措施来同步
 *		关系，因此我们无需费心。临时关系也不需要同步。
 */
void smgrwrite(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blocknum,
		  char *fc_buffer, bool fc_skipFsync)
{
	smgrsw[fc_reln->smgr_which].smgr_write(fc_reln, fc_forknum, fc_blocknum,
										fc_buffer, fc_skipFsync);
}


/*
 *	smgrwriteback() -- 触发内核写回指定范围的
 *					   块。
 */
void smgrwriteback(SMgrRelation fc_reln, ForkNumber fc_forknum, BlockNumber fc_blocknum,
			  BlockNumber fc_nblocks)
{
	smgrsw[fc_reln->smgr_which].smgr_writeback(fc_reln, fc_forknum, fc_blocknum,
											fc_nblocks);
}

/*
 *	smgrnblocks() -- 计算提供的关系中的块数量。
 */
BlockNumber smgrnblocks(SMgrRelation fc_reln, ForkNumber fc_forknum)
{
	BlockNumber fc_result;

	/* 检查并返回我们是否获得了块数的缓存值。 */
	fc_result = smgrnblocks_cached(fc_reln, fc_forknum);
	if (fc_result != InvalidBlockNumber)
		return fc_result;

	fc_result = smgrsw[fc_reln->smgr_which].smgr_nblocks(fc_reln, fc_forknum);

	fc_reln->smgr_cached_nblocks[fc_forknum] = fc_result;

	return fc_result;
}

/*
 *	smgrnblocks_cached() -- 获取提供的关系中的缓存块数量。
 *
 * 在不处于恢复状态且关系分叉大小未缓存时，返回 InvalidBlockNumber。
 */
BlockNumber smgrnblocks_cached(SMgrRelation fc_reln, ForkNumber fc_forknum)
{
	/*
	 * 目前，此函数仅在恢复过程中使用缓存值，因为缺乏
	 * 共享失效机制来处理文件大小的变化。其他地方的代码
	 * 读取 smgr_cached_nblocks 并处理过时数据。
	 */
	if (InRecovery && fc_reln->smgr_cached_nblocks[fc_forknum] != InvalidBlockNumber)
		return fc_reln->smgr_cached_nblocks[fc_forknum];

	return InvalidBlockNumber;
}

/*
 *	smgrtruncate() -- 将提供关系的给定分叉截断为
 *					  每个指定的块数
 *
 * 向后兼容版本的 smgrtruncate2()，以便于外部
 * 调用者使用。这个版本未在 PostgreSQL 核心代码中使用，也不能在
 * 临界区中使用。
 */
void smgrtruncate(SMgrRelation fc_reln, ForkNumber *fc_forknum, int fc_nforks,
			 BlockNumber *fc_nblocks)
{
	BlockNumber fc_old_nblocks[MAX_FORKNUM + 1];

	for (int fc_i = 0; fc_i < fc_nforks; ++fc_i)
		fc_old_nblocks[fc_i] = smgrnblocks(fc_reln, fc_forknum[fc_i]);

	smgrtruncate2(fc_reln, fc_forknum, fc_nforks, fc_old_nblocks, fc_nblocks);
}

/*
 * smgrtruncate2() -- 将提供关系的给定分叉截断为
 *					  每个指定的块数
 *
 * 截断是立即执行的，因此这不能回滚。
 *
 * 调用者必须在关系上持有 AccessExclusiveLock，以确保
 * 其他后端在再次访问任何关系的分叉之前接收此函数发送的
 * smgr 失效事件。应在 old_nblocks 中提供分叉的当前大小。
 * 该函数通常应在临界区中调用，但必须在临界区外检查
 * 当前大小，并且在此过程中不得调用与此关系相关的中断或 smgr 函数。
 */
void smgrtruncate2(SMgrRelation fc_reln, ForkNumber *fc_forknum, int fc_nforks,
			  BlockNumber *fc_old_nblocks, BlockNumber *fc_nblocks)
{
	int			fc_i;

	
/*
	 * 清除即将删除的块的任何缓存。bufmgr 将直接丢弃它们，而不必写入内容。
	 */
	DropRelFileNodeBuffers(fc_reln, fc_forknum, fc_nforks, fc_nblocks);

	/*
	 * 发送共享无效消息，以强制其他后端关闭它们可能对该关系的任何 smgr 引用。 这很有用，因为它们可能已经有指向被删除的段的打开文件指针，和/或 smgr_targblock 变量指向新关系结束之外。 （无效消息也会返回到我们的后端，导致一个可能不必要的本地 smgr 刷新。 但我们不认为这是一条对性能关键的路径。）与 unlink 代码一样，我们希望在开始更改磁盘上的内容之前确保发送此消息。
	 */
	CacheInvalidateSmgr(fc_reln->smgr_rnode);

	/* 执行截断 */
	for (fc_i = 0; fc_i < fc_nforks; fc_i++)
	{
		/* 如果遇到错误，则使缓存大小无效。 */
		fc_reln->smgr_cached_nblocks[fc_forknum[fc_i]] = InvalidBlockNumber;

		smgrsw[fc_reln->smgr_which].smgr_truncate(fc_reln, fc_forknum[fc_i],
											   fc_old_nblocks[fc_i], fc_nblocks[fc_i]);

		/*
		 * 我们不妨更新本地的 smgr_cached_nblocks 值。 此函数发送的 smgr 缓存无效消息将导致其他后端使它们的 smgr_fsm_nblocks 和 smgr_vm_nblocks 复制失效，并在下一个命令边界时使这些值也失效。 但这些确保在那之前它们不会完全错误。
		 */
		fc_reln->smgr_cached_nblocks[fc_forknum[fc_i]] = fc_nblocks[fc_i];
	}
}

/*
 *	smgrimmedsync() -- 强制将指定关系写入稳定存储。
 *
 *		同步地强制将所有以前写入的指定关系写入磁盘。
 *
 *		这对于构建全新的关系（例如，新的索引）非常有用。 我们可以仅仅使用 smgrwrite 或 smgrextend 将已完成的索引页面写入磁盘，然后在提交事务之前 fsync 完成的索引文件，而不是逐步记录 WAL 日志索引构建步骤。 （这对于崩溃恢复是足够的，因为它有效地重复了对已完成索引强制检查点的操作。 但如果希望使用 WAL 日志进行 PITR 或复制，则 *不* 足够：在这种情况下，我们还必须进行 WAL 条目。）
 *
 *		前面的写入应该将 skipFsync 指定为 true，以避免重复的 fsync。
 *
 *		注意，如果有任何可能存在的脏缓冲区，您需要先执行 FlushRelationBuffers()；否则，sync 并没有太大意义。
 */
void smgrimmedsync(SMgrRelation fc_reln, ForkNumber fc_forknum)
{
	smgrsw[fc_reln->smgr_which].smgr_immedsync(fc_reln, fc_forknum);
}

/*
 * AtEOXact_SMgr
 *
 * 此例程在事务提交或中止期间被调用（它并不特别关心哪种情况）。 所有瞬态 SMgrRelation 对象都将被关闭。
 *
 * 我们这样做是为了在希望瞬态 SMgrRelations 存在一段时间（以摊销多个块的盲写成本）和需要它们不永久存在（因为我们可能正在保持对底层文件的内核文件描述符打开，且需要确保在文件被删除时合理地关闭它们）之间进行妥协。
 */
void AtEOXact_SMgr(void)
{
	dlist_mutable_iter fc_iter;

	/*
	 * 清除所有未拥有的 SMgrRelations。 我们依赖 smgrclose() 从列表中删除每一个。
	 */
	dlist_foreach_modify(fc_iter, &unowned_relns)
	{
		SMgrRelation fc_rel = dlist_container(SMgrRelationData, node,
										   fc_iter.cur);

		Assert(fc_rel->smgr_owner == NULL);

		smgrclose(fc_rel);
	}
}

/*
 * 当我们被命令通过 ProcSignalBarrier 释放所有打开文件时调用此例程。
 */
bool ProcessBarrierSmgrRelease(void)
{
	smgrreleaseall();
	return true;
}
